package model.compresion.artmetico;

import java.io.IOException;

import model.compresion.TablaProbabilidades;
import model.extras.bitArray.BitArray;
import model.extras.bitArray.BitArrayException;
import model.extras.bitArray.BitArrayStream;

public class CompresorAritmetico {
	
	private TablaProbabilidades tabla;
	private BitArray  piso ;
	private BitArray techo;
	private BitArray rango;
	private BitArrayStream output;
	private int underflow;
	private int overflow;
	private int ufAnterior;
	
	
	public CompresorAritmetico() throws Exception{
		this.piso = new BitArray();
		this.piso.SetPiso();
		this.overflow = 0;
		this.underflow = 0;
		this.techo = new BitArray();
		this.techo.SetTecho();
		this.rango = new BitArray();
		this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));
		this.output = new BitArrayStream();
	}
	

	public TablaProbabilidades getTabla() {
		return tabla;
	}


	public BitArray getPiso() {
		return piso;
	}


	public BitArray getTecho() {
		return techo;
	}


	public BitArray getRango() {
		return rango;
	}


	public BitArrayStream getOutput() {
		return output;
	}


	public int getUnderflow() {
		return underflow;
	}


	public int getOverflow() {
		return overflow;
	}
/*
 * El Arraylist tiene q tener solo el float con las probabilidades, NO las frecuencias
 * Si es el eof emito el piso directamente, y la probabilidad es la de la ultioma pos del Arraylist
 * Si es el de escape , es igual a cualquier otro caracter pero la prob es el anteultimo del arraylist
 * el rs va de la 0 a la 65537, la 65536 es escape y 65537  el eof
 */

	public void calcularIntervalo(TablaProbabilidades TablaP,int pos ) throws Exception{//este paramentro es el caracter que quiere comprimir
		this.tabla = TablaP;
		//System.out.println("El piso inicial  es "+this.piso.GetNumero());
		//System.out.println("El techo inicial es"+ this.techo.GetNumero());
		int it =0;
		long res = 0;
		long contador = 0;
		boolean encontrado = false;
		while (!encontrado){
			if (pos != it){
				//res = (long) (this.piso.GetNumero() + (this.tabla.GetSimbolo(it).getProbabilidad()/this.tabla.getTotal()) * this.rango.GetNumero());
				contador = contador + this.tabla.GetSimbolo(it).getProbabilidad();
				//this.piso.SetNumero(res); 
				it++;
			}else{
			    long aux = (long) (this.piso.GetNumero() + ((contador) * (this.rango.GetNumero()))/this.tabla.getTotal());
				res = (long) (this.piso.GetNumero() + ((((contador) + this.tabla.GetSimbolo(it).getProbabilidad())* (this.rango.GetNumero())/this.tabla.getTotal()) )-1);
				encontrado = true;
				//System.out.println("Se comprimio "+it+ " con probabilidad "+this.tabla.GetSimbolo(it).getProbabilidad()+"/"+this.tabla.getTotal());
				this.piso.SetNumero(aux);
				this.techo.SetNumero(res);	
			}
		}
		if(pos != 65537){
		    //System.out.println("El piso dps de encontrar el caracter es  "+this.piso.GetNumero() +"\t"+this.piso.ToBinaryString());
		
			//System.out.println("El techo dps de encontrar el caracter es "+this.techo.GetNumero()+"\t"+this.techo.ToBinaryString());
			

			analizarOutput();//deberia encontrar si o si
			//System.out.println("El piso final es: "+this.piso.GetNumero()+"\t"+this.piso.ToBinaryString());
			//System.out.println("El techo final es: "+this.techo.GetNumero()+"\t"+this.techo.ToBinaryString());
			
			this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));	
			
			//System.out.println();
			//System.out.println("La salida es "+this.output.ToBinaryString());
			//System.out.println();
			//System.out.println();
		}else emitirPiso();
		
	}
	public void calcularIntervalo(TablaProbabilidades TablaP,int pos,boolean hola ) throws Exception{//este paramentro es el caracter que quiere comprimir
		this.tabla = TablaP;

		//System.out.println("El piso inicial  es "+this.piso.GetNumero());
		//System.out.println("El techo inicial es"+ this.techo.GetNumero());
		
		int it =65534;
		long res = 0;
		long contador = 0;
		boolean encontrado = false;
		while (!encontrado){
			if (pos != it){
				//res = (long) (this.piso.GetNumero() + (this.tabla.GetSimbolo(it).getProbabilidad()/this.tabla.getTotal()) * this.rango.GetNumero());
				contador = contador + this.tabla.GetSimbolo(it).getProbabilidad();
				//this.piso.SetNumero(res); 
				it++;
			}else{
			    long aux = (long) (this.piso.GetNumero() + ((contador) * (this.rango.GetNumero()))/this.tabla.getTotal());
				res = (long) (this.piso.GetNumero() + ((((contador) + this.tabla.GetSimbolo(it).getProbabilidad())* (this.rango.GetNumero())/this.tabla.getTotal()) )-1);
				encontrado = true;
			    //System.out.println("Se comprimio "+it+ " con probabilidad "+this.tabla.GetSimbolo(it).getProbabilidad()+"/"+this.tabla.getTotal());
				this.piso.SetNumero(aux);
				this.techo.SetNumero(res);	
			}
		}
			//System.out.println("El piso dps de encontrar el caracter es  "+this.piso.GetNumero()+"\t"+this.piso.ToBinaryString());
			
			//System.out.println("El techo dps de encontrar el caracter es "+this.techo.GetNumero()+"\t"+this.techo.ToBinaryString());

			analizarOutput();//deberia encontrar si o si
			//System.out.println("El piso final es: "+this.piso.GetNumero()+"\t"+this.piso.ToBinaryString());
			//System.out.println("El techo final es: "+this.techo.GetNumero()+"\t"+this.techo.ToBinaryString());
			this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));	
		    //System.out.println();
			//System.out.println("La salida es "+this.output.ToBinaryString());
			//System.out.println();
			//System.out.println();
			
	}
	
	private void emitirPiso() throws BitArrayException {
		boolean aux;
		//System.out.println("EL TECHO FINAL A EMITIR ES " + this.techo.ToBinaryString());
		for (int i = 0; i < this.techo.Length(); i++) {
			try {
				this.output.PutBit(this.techo.BitAt(i));
				for(int j = this.underflow; j > 0; j--){//emito tantos caracteres inversos como uf tenia
					aux = techo.BitAt(0);
					this.output.PutBit(!aux);
				}
				this.underflow=0;
				
			} catch (BitArrayException e) {
				
				e.printStackTrace();
			}
		}
		//System.out.println("La salida es "+this.output.ToBinaryString());
		
	}


	public void analizarOutput () throws IOException{
		
		try {
			
			boolean borraUF = false;
			this.ufAnterior = this.underflow;
			overflow();
			 //emito lo q corresponde
			//System.out.println("El overflow de este paso es: "+this.overflow);

			if ( this.overflow > 0){ 
				//of = true en param 3
				//param 4 con q completar el array
				this.piso = BitArray.CompletarBits(this.overflow, this.piso,true ,false ); //completo
				this.techo = BitArray.CompletarBits(this.overflow, this.techo,true ,true );	
				this.overflow = 0;
				borraUF = true;
			}

			else borraUF = false;
			underflow();//reviso el underflow y sumo el acumulador

			//System.out.println("El underflow de este paso es: "+this.underflow);
				if(this.underflow > this.ufAnterior && !borraUF ==true){//hay underflow, los cuento para luego eliminar y saber cuanto emitir la proxima vez
					this.piso = BitArray.CompletarBits(this.underflow-this.ufAnterior, this.piso,false ,false ); //completo
					this.techo = BitArray.CompletarBits(this.underflow-this.ufAnterior, this.techo,false ,true );	
				}else{
					if(this.underflow > 0 && borraUF ==true){//hay underflow, los cuento para luego eliminar y saber cuanto emitir la proxima vez
						this.piso = BitArray.CompletarBits(this.underflow, this.piso,false ,false ); //completo
						this.techo = BitArray.CompletarBits(this.underflow, this.techo,false ,true );	
					} 
				
			}
			
		} catch (Exception e) {
			System.out.println("Error al calcular Uf y Of");
			e.printStackTrace();
		}

	}
	

	private void underflow() throws Exception {
		boolean iguales;
		if( this.piso.BitAt(0) == this.techo.BitAt(0)) System.out.println("ERROR EN UN UF");
		boolean bpiso = this.piso.BitAt(1);//leo desde el 2 porq los primeros ya se q son distintos porq no hay overflow o si hubo ya se corto
		boolean bpisoSig = false;
		boolean btechoSig = false;
		boolean btecho = this.techo.BitAt(1);
		boolean Pcruzado= this.piso.BitAt(0) != this.piso.BitAt(1);
		boolean Tcruzado= this.techo.BitAt(0) != this.techo.BitAt(1);
		boolean biento = true;
		iguales = bpiso == btecho;
		int i=2;
		if(Tcruzado && Pcruzado){
			while (iguales == false && biento && i < this.piso.Length()){
				this.underflow++;
				bpisoSig = this.piso.BitAt(i);
				btechoSig = this.techo.BitAt(i);
				iguales = bpiso == btecho;
				i++;
				if (!( bpiso == bpisoSig && btecho ==btechoSig)) biento = false ;
				}
		}
		
	}

	private void overflow() throws Exception {
		int of = 0;
		int j = 0;
		boolean iguales;
		boolean aux = false;
		boolean bpiso = this.piso.BitAt(j);
		boolean btecho = this.techo.BitAt(j);
		iguales = bpiso == btecho;
		while (iguales == true && j < this.piso.Length()){//tengo q ver el contador de uf q tenia para ver si emito algo inverso
			this.overflow++;
			this.output.PutBit(piso.BitAt(j));
			for(int i = this.underflow; i > 0; i--){//emito tantos caracteres inversos como uf tenia
				aux = piso.BitAt(j);
				this.output.PutBit(!aux);
			}
			this.underflow=0;
			j++;
			iguales = this.piso.BitAt(j) == this.techo.BitAt(j);
			}
	}
}




